///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_UTILITY_RANDOM_H_
#define _BUOLA_UTILITY_RANDOM_H_

#include <buola/buola.h>

#include <random>
#include <iterator>
#include <type_traits>

namespace buola {

///\ingroup utility
///@{

namespace random {

///type of the default random engine
typedef std::mt19937_64 CDefaultEngine;

///returns the default random engine

///The engine is seeded using the current time. If you need your program to behave the same every time you run, you
///can seed it with:
///
///engine().seed(0);
CDefaultEngine &engine();

template<typename tEngine,typename tDistribution>
class CGenerator
{
public:
    CGenerator(tEngine &pEngine,const tDistribution &pDistribution)
        :   mEngine(pEngine)
        ,   mDistribution(pDistribution)
    {}

    typename tDistribution::result_type operator()()
    {
        return mDistribution(mEngine);
    }
    
private:
    tEngine &mEngine;
    tDistribution mDistribution;
};

template<typename tType,typename tEnable=void>
class CDefaultDistribution;

template<typename tType>
class CDefaultDistribution<tType,typename std::enable_if<std::is_floating_point<tType>::value,void>::type>
                                    : public std::uniform_real_distribution<tType>
{
public:
    typedef std::uniform_real_distribution<tType> tBase;
    
    CDefaultDistribution()
        :   tBase()
    {}

    CDefaultDistribution(tType pMax)
        :   tBase(0,pMax)
    {}

    CDefaultDistribution(tType pMin,tType pMax)
        :   tBase(pMin,pMax)
    {}
};

template<typename tType>
class CDefaultDistribution<tType,typename std::enable_if<std::is_integral<tType>::value,void>::type>
                                    : public std::uniform_int_distribution<tType>
{
public:
    typedef std::uniform_int_distribution<tType> tBase;

    CDefaultDistribution()
        :   tBase()
    {}

    CDefaultDistribution(tType pMax)
        :   tBase(0,pMax)
    {}

    CDefaultDistribution(tType pMin,tType pMax)
        :   tBase(pMin,pMax)
    {}
};

template<>
class CDefaultDistribution<bool,void> : public std::bernoulli_distribution
{
public:
    typedef std::bernoulli_distribution tBase;

    CDefaultDistribution(double pProb=0.5)
        :   tBase(pProb)
    {}
};

template<typename tType,typename... tParams>
inline CGenerator<CDefaultEngine,CDefaultDistribution<tType>> generator(tParams&&... pParams)
{
    return CGenerator<CDefaultEngine,CDefaultDistribution<tType>>(engine(),
                                     CDefaultDistribution<tType>(std::forward<tParams>(pParams)...));
}

template<typename tType,typename... tParams>
tType get(tParams&&... pParams)
{
    return generator<tType>(std::forward<tParams>(pParams)...)();
}

template<typename tIt>
inline void shuffle(const tIt &pB,const tIt &pE)
{
    shuffle(pB,pE,engine());
}

template<typename tRange>
inline void shuffle(tRange &&pRange)
{
    shuffle(pRange.begin(),pRange.end());
}

template<typename tIt>
inline tIt choose(const tIt &pB,const tIt &pE)
{
    typedef typename std::iterator_traits<tIt>::difference_type tDiff;
    return pB+get<tDiff>(pE-pB-1);
}

template<typename tRange>
inline auto choose(tRange &&pRange) -> decltype(*pRange.begin())
{
    return *choose(pRange.begin(),pRange.end());
}

template<typename tIt>
inline void fill(tIt pB,const tIt &pE)
{
    typedef typename std::iterator_traits<tIt>::value_type tType;

    while(pB!=pE)
    {
        *pB=get<tType>();
        ++pB;
    }
}

/*namespace random*/ }

///@}

/*namespace buola*/ }

#endif
